﻿Imports System.Net.Sockets
Imports MapleLib.MapleCryptoLib
Imports System.Net
Imports MapleLib.PacketLib
Imports System.Threading
Imports MMSServerPayBack.Functions
Imports System.Data.SqlClient

Public Class MapleClient
#Region "Client"
    Public LoggedIn As Boolean
    Public AccountName As String
    Public AccountID As Integer

    Public Function LoadCharacters() As List(Of MapleCharacter)
        Dim chars As New List(Of MapleCharacter)
        Dim sql As New MSSQL
        Dim query As String = String.Format("SELECT * FROM tbl_characters WHERE AccountID='{0}'", AccountID)
        Using reader As SqlDataReader = sql.OpenRS(query)
            While reader.Read()
                Dim c As New MapleCharacter() With {
                    .Name = reader("name"),
                    .Id = reader("id"),
                    .Gender = reader("gender"),
                    .Skin = reader("skin"),
                    .Face = reader("face"),
                    .Hair = reader("hair"),
                    .Job = reader("job"),
                    .Int = reader("_int"),
                    .Str = reader("str"),
                    .Dex = reader("dex"),
                    .Luk = reader("luk"),
                    .Level = reader("lvl"),
                    .curHP = reader("curHP"),
                    .curMP = reader("curMP"),
                    .maxHP = reader("maxHP"),
                    .maxMP = reader("maxMP"),
                    .ap = reader("ap"),
                    .sp = reader("sp"),
                    .exp = reader("exp"),
                    .Fame = reader("fame"),
                    .MapId = reader("map"),
                    .SpawnPoint = reader("spawnpoint")
                }
                chars.Add(c)
            End While
        End Using
        sql.Close()
        Return chars
    End Function
#End Region

#Region "Data"
    Private mSocket As Socket
    Private mHost As String
    Private mDisconnected As Integer

    Private mReceiveBuffer As Byte() = Nothing

    Private mSendSegments As New LockFreeQueue(Of ByteArraySegment)()
    Private mSending As Integer

    Public _RIV As MapleCrypto
    Public _SIV As MapleCrypto

    Private _main As Main
    Public Sub New(ByVal pSocket As Socket, ByVal RIV As MapleCrypto, ByVal SIV As MapleCrypto, ByVal through As Main)
        _main = through
        _RIV = RIV
        _SIV = SIV
        mSocket = pSocket
        mReceiveBuffer = New Byte(Settings.MAX_RECEIVE_BUFFER - 1) {}
        mHost = DirectCast(mSocket.RemoteEndPoint, IPEndPoint).Address.ToString()
        _main.WriteLine(String.Format("[{0}:{1}] Connected", mHost, DirectCast(mSocket.LocalEndPoint, IPEndPoint).Port.ToString), 0)
        WaitForData()
    End Sub

    Public Sub WaitForData()
        If Not IsNothing(mSocket) Then
            Dim SI As New SocketInfo(mSocket, 4)
            WaitForData(SI)
        End If
    End Sub

    Public Sub WaitForData(ByVal SI As SocketInfo)
        Try
            mSocket.BeginReceive(SI.DataBuffer, SI.Index, SI.DataBuffer.Length - SI.Index, SocketFlags.None, New AsyncCallback(AddressOf OnDataReceived), SI)
        Catch ex As Exception

        End Try
    End Sub

    Public Sub OnDataReceived(ByVal iar As IAsyncResult)
        Dim socketInfo__1 As SocketInfo = DirectCast(iar.AsyncState, SocketInfo)
        Try
            Dim received As Integer = socketInfo__1.Socket.EndReceive(iar)
            If received = 0 Then
                Disconnect()
                Return
            End If

            socketInfo__1.Index += received

            If socketInfo__1.Index = socketInfo__1.DataBuffer.Length Then
                Select Case socketInfo__1.State
                    Case SocketInfo.StateEnum.Header
                        If socketInfo__1.NoEncryption Then
                            WaitForData(socketInfo__1)
                        Else
                            Dim headerReader As New PacketReader(socketInfo__1.DataBuffer)
                            Dim packetHeaderB As Byte() = headerReader.ToArray()
                            Dim packetHeader As Integer = headerReader.ReadInt()
                            Dim packetLength As Short = CShort(MapleCrypto.getPacketLength(packetHeader))
                            If Not _RIV.checkPacketToServer(BitConverter.GetBytes(packetHeader)) Then
                                _main.WriteLine("[Error] Packet check failed. Disconnecting client.")
                                Disconnect()
                            End If
                            socketInfo__1.State = SocketInfo.StateEnum.Content
                            socketInfo__1.DataBuffer = New Byte(packetLength - 1) {}
                            socketInfo__1.Index = 0
                            WaitForData(socketInfo__1)
                        End If
                        Exit Select
                    Case SocketInfo.StateEnum.Content
                        Dim data As Byte() = socketInfo__1.DataBuffer
                        If socketInfo__1.NoEncryption Then
                            WaitForData()
                        Else
                            _RIV.crypt(data)
                            MapleCustomEncryption.Decrypt(data)
                            _main.WriteLine(ByteArrayToStr(data))
                            PacketHandler.HandleRecv(data, Me, _main)
                            WaitForData()
                        End If
                        Exit Select
                End Select
            Else
                '_main.WriteLine(String.Format("[Warning] Not enough data")
                WaitForData(socketInfo__1)
            End If
        Catch generatedExceptionName As ObjectDisposedException
            _main.WriteLine("[Error] Session.OnDataReceived: Socket has been closed")
        Catch se As SocketException
            If se.ErrorCode <> 10054 Then
                _main.WriteLine("[Error] Session.OnDataReceived: " & se.ToString)
            Else
                _main.WriteLine("[Error] Session.OnDataReceived: Socket has been closed")
                Disconnect()
            End If
        Catch e As Exception
            _main.WriteLine("[Error] Session.OnDataReceived: " & e.ToString)
        End Try

    End Sub

    Public Sub Disconnect()
        On Error Resume Next
        If Interlocked.CompareExchange(mDisconnected, 1, 0) = 0 Then
            _main.WriteLine(String.Format("[{0}:{1}] Disconnected", mHost, DirectCast(mSocket.LocalEndPoint, IPEndPoint).Port.ToString))
            mSocket.Shutdown(SocketShutdown.Both)
            mSocket.Close()
            'Server.ClientDisconnected(Me)
            'Me.Dispose()
        End If
    End Sub

    Friend Sub SendHandshake(ByVal pBuild As UShort, ByVal pPatch As UShort, ByVal pReceiveIV As Byte(), ByVal pSendIV As Byte())
        Dim writer As New PacketWriter
        writer.WriteShort(SendHeaders.HandShake)
        writer.WriteShort(pBuild)
        writer.WriteShort(pPatch)
        writer.WriteByte(49)
        writer.WriteBytes(pReceiveIV)
        writer.WriteBytes(pSendIV)
        writer.WriteByte(8)
        Send(writer.ToArray)
    End Sub

    Private Sub Send(ByVal pBuffer As Byte())
        If mDisconnected <> 0 Then
            Return
        End If
        mSendSegments.Enqueue(New ByteArraySegment(pBuffer))
        If Interlocked.CompareExchange(mSending, 1, 0) = 0 Then
            BeginSend()
        End If
    End Sub

    Private Sub BeginSend()
        Dim args As New SocketAsyncEventArgs()
        AddHandler args.Completed, AddressOf EndSend
        Dim segment As ByteArraySegment = mSendSegments.[Next]
        args.SetBuffer(segment.Buffer, segment.Start, segment.Length)
        Try
            If Not mSocket.SendAsync(args) Then
                EndSend(Nothing, args)
            End If
        Catch generatedExceptionName As ObjectDisposedException
        End Try
    End Sub

    Private Sub EndSend(ByVal sender As Object, ByVal pArguments As SocketAsyncEventArgs)
        If mDisconnected <> 0 Then
            Return
        End If
        If pArguments.BytesTransferred <= 0 Then
            If pArguments.SocketError <> SocketError.Success Then
                _main.WriteLine(String.Format("[{0}] Send Error: {1}", mHost, pArguments.SocketError))
            End If
            Disconnect()
            Return
        End If
        If mSendSegments.[Next].Advance(pArguments.BytesTransferred) Then
            mSendSegments.Dequeue()
        End If
        If mSendSegments.[Next] IsNot Nothing Then
            BeginSend()
        Else
            mSending = 0
        End If
    End Sub

    Public Sub SendPacket(ByVal packet As PacketWriter)
        SendPacket(packet.ToArray())
    End Sub

    Public Sub SendPacket(ByVal input As Byte())
        Try
            Dim cryptData As Byte() = input
            Dim sendData As Byte() = New Byte(cryptData.Length + 3) {}
            Dim header As Byte() = _SIV.getHeaderToClient(cryptData.Length)

            MapleCustomEncryption.Encrypt(cryptData)
            _SIV.crypt(cryptData)

            System.Buffer.BlockCopy(header, 0, sendData, 0, 4)
            System.Buffer.BlockCopy(cryptData, 0, sendData, 4, cryptData.Length)
            Send(sendData)

        Catch ex As Exception
            _main.WriteLine(ex.ToString)
        End Try
    End Sub
#End Region
End Class
